<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <link rel="stylesheet" href="./bootstrap/bootstrap.min.css" />
    <link rel="stylesheet" href="./bootstrap/bootstrap-theme.min.css" />
    <style>
      * {
        margin: 0;
        padding: 0;
        box-sizing: border-box;
      }

      .wrap {
        width: 100px;
        height: 40px;
        background-color: red;
        text-align: center;
      }

      .wrap p {
        width: 100%;
        height: 100%;
        line-height: 2;
        text-align: center;
      }

      #file {
        position: absolute;
        left: 0;
        top: 0;
        width: 100px;
        height: 40px;
        display: block;
        opacity: 0;
      }

      .progress {
        position: relative;
      }

      .progress-bar {
        transition: width 0.3s ease;
      }

      .progress .value {
        position: absolute;
        color: #ff9800;
        left: 50%;
      }

      .container {
        width: 500px;
      }

      .row {
        border-bottom: 1px solid gray;
        padding: 10px;
      }

      .hidden {
        display: none;
      }
      .mrb20 {
        margin: 20px 0;
      }
    </style>
    <title>上传文件</title>
  </head>

  <body>
    <div class="container">
      <div class="row">
        <div class="col-md-4 mrb20">点击按钮开始上传文件</div>
        <div class="col-md-8">
          <div class="wrap btn btn-default">
            <input type="file" id="file" multiple />
            <p>上传文件</p>
          </div>
        </div>
      </div>
      <div class="row" id="process1" style="display: none;">
        <div class="col-md-4">校验文件进度</div>
        <div class="col-md-8">
          <div class="progress">
            <div
              id="checkProcessStyle"
              class="progress-bar"
              style="width: 0%;"
            ></div>
            <p id="checkProcessValue" class="value">0%</p>
          </div>
        </div>
      </div>
      <div class="row" id="process2" style="display: none;">
        <div class="col-md-4">上传文件进度</div>
        <div class="col-md-8">
          <div class="progress">
            <div
              id="uploadProcessStyle"
              class="progress-bar"
              style="width: 0%;"
            ></div>
            <p id="uploadProcessValue" class="value">0%</p>
          </div>
        </div>
      </div>
      <div class="row">
        <div class="col-md-4 mrb20">multer上传文件</div>
        <form
          id="upload"
          class="col-md-8 mrb20"
          action="/multerUpload"
          method="post"
          enctype="multipart/form-data"
          target="stop"
          οnsubmit="submitForm();"
        >
          <input id="files" type="file" name="file" multiple />
          <!-- 这里的multiple属性是允许多文件上传的，单文件上传可以不写此属性（最好都写上）-->
          <input type="submit" value="上传" />
          <!-- 阻止提交跳转页面 -->
          <iframe name="stop" style="display: none;"></iframe>
        </form>
      </div>
    </div>
    <script src="./lib/jquery-1.10.2.min.js"></script>
    <script src="./bootstrap/bootstrap.min.js"></script>
    <script src="./lib/spark-md5.min.js"></script>
    <script>
      let baseUrl = "http://localhost:5000";
      let chunkSize = 5 * 1024 * 1024; // 分片大小
      let fileSize = 0;
      let file = null;
      let hasUploaded = 0;
      let chunks = 0;
      $("#file").on("change", function () {
        // 单个文件
        // file = this.files[0];
        // 批量上传
        for (let i = 0; i < this.files.length; i++) {
          file = this.files[i];
          fileSize = file.size;
          responseChange(file);
        }
      });

      // 0.响应点击
      async function responseChange(file) {
        // 第一步：按照 修改时间+文件名称+最后修改时间-->MD5
        // 显示文件校验进度
        $("#process1").slideDown(200);
        // 开始校验
        let fileMd5Value = await md5File(file);
        // 第二步：校验文件的MD5
        let result = await checkFileMD5(file.name, fileMd5Value);
        // 如果文件已存在, 就秒传
        if (result.file) {
          alert("文件已秒传");
          return;
        }
        // 显示文件上传进度
        $("#process2").slideDown(200);
        // 第三步：检查并上传MD5
        await checkAndUploadChunk(fileMd5Value, result.chunkList);
        // 第四步: 通知服务器所有分片已上传完成
        notifyServer(fileMd5Value, file);
      }

      // 1.修改时间+文件名称+最后修改时间-->MD5
      function md5File(file) {
        return new Promise((resolve, reject) => {
          var blobSlice =
              File.prototype.slice ||
              File.prototype.mozSlice ||
              File.prototype.webkitSlice,
            chunkSize = file.size / 100, // 分成100份
            chunks = 100,
            currentChunk = 0,
            spark = new SparkMD5.ArrayBuffer(),
            fileReader = new FileReader();

          fileReader.onload = function (e) {
            console.log("read chunk nr", currentChunk + 1, "of", chunks);
            spark.append(e.target.result); // Append array buffer
            currentChunk++;

            if (currentChunk < chunks) {
              loadNext();
            } else {
              let cur = +new Date();
              console.log("finished loading");
              // alert(spark.end() + '---' + (cur - pre)); // Compute hash
              let result = spark.end();
              resolve(result);
            }
          };

          fileReader.onerror = function () {
            console.warn("oops, something went wrong.");
          };

          function loadNext() {
            var start = currentChunk * chunkSize,
              end =
                start + chunkSize >= file.size ? file.size : start + chunkSize;

            fileReader.readAsArrayBuffer(blobSlice.call(file, start, end));
            $("#checkProcessStyle").css({
              width: currentChunk + 1 + "%",
            });
            $("#checkProcessValue").html(currentChunk + 1 + "%");
          }

          loadNext();
        });
      }

      // 2.校验文件的MD5
      function checkFileMD5(fileName, fileMd5Value) {
        return new Promise((resolve, reject) => {
          let url =
            baseUrl +
            "/check/file?fileName=" +
            fileName +
            "&fileMd5Value=" +
            fileMd5Value;
          $.getJSON(url, function (data) {
            resolve(data);
          });
        });
      }

      // 3.上传chunk
      async function checkAndUploadChunk(fileMd5Value, chunkList) {
        chunks = Math.ceil(fileSize / chunkSize); // 所有的多少份
        hasUploaded = chunkList.length; // 已经上传了多少份 [] 从索引0开始
        for (let i = 0; i < chunks; i++) {
          let exit = chunkList.indexOf(i + "") > -1;
          // 如果已经存在, 则不用再上传当前块
          if (!exit) {
            let index = await upload(i, fileMd5Value, chunks); // 一个个执行上传的
            hasUploaded++;
            let radio = Math.floor((hasUploaded / chunks) * 100); // 计算上传的比例
            $("#uploadProcessStyle").css({
              width: radio + "%",
            });
            $("#uploadProcessValue").html(radio + "%");
          }
        }
      }

      // 3-2. 上传chunk
      function upload(i, fileMd5Value, chunks) {
        return new Promise((resolve, reject) => {
          //构造一个表单，FormData是HTML5新增的
          let end =
            (i + 1) * chunkSize >= file.size ? file.size : (i + 1) * chunkSize;
          let form = new FormData();
          form.append("data", file.slice(i * chunkSize, end)); //file对象的slice方法
          form.append("total", chunks); //总片数
          form.append("index", i); //当前是第几片
          form.append("fileMd5Value", fileMd5Value);
          $.ajax({
            url: baseUrl + "/upload",
            type: "POST",
            data: form, //刚刚构建的form数据对象
            async: true, //异步
            processData: false, //很重要，告诉jquery不要对form进行处理
            contentType: false, //很重要，指定为false才能形成正确的Content-Type
            success: function (data) {
              resolve(data.desc);
            },
          });
        });
      }

      // 第四步: 通知服务器所有分片已上传完成
      function notifyServer(fileMd5Value, file) {
        let url =
          baseUrl +
          "/merge?md5=" +
          fileMd5Value +
          "&fileName=" +
          file.name +
          "&size=" +
          file.size;
        $.getJSON(url, function (data) {
          alert("上传成功");
        });
      }

      function submitForm() {
        // var form = document.getElementById("upload"),
        //   formData = new FormData(form);
        // $.ajax({
        //   url: baseUrl + "/multerUpload",
        //   type: "post",
        //   data: formData,
        //   processData: false,
        //   contentType: false,
        //   success: function (res) {
        //     if (res) {
        //       alert(res);
        //     }
        //     console.log(res);
        //   },
        //   error: function (err) {
        //     alert("网络连接失败,稍后重试", err);
        //   },
        // });
        // $("#upload").ajaxSubmit(function (message) {
        //   // 对于表单提交成功后处理，message为表单正常提交后返回的内容
        //   console.log(message);
        // });
        return false;
      }
    </script>
  </body>
</html>
